Esta es la continuación de una serie de posts sobre el manejo de excepciones en Java. Si no has visto la parte 1 puedes consultarla aquí.
Continuando con el post anterior ¿Como le hacemos para que en un mismo catch podamos capturar mas de un tipo de excepción? A partir de Java 7 se agregó una nueva característica que nos ayuda a resolver esto usando el caracter pipe. A continuación un ejemplo:
import java.util.Scanner;
import java.util.InputMismatchException;
public class Division {
public static void main(String[] args) {
try{
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
System.out.println("Resultado = " + (a/b));
}
catch(InputMismatchException | ArithmeticException e){
System.out.println("No ingresaste un número válido ó una excepción aritmética a ocurrido. Bye!");
}
}
}
En el ejemplo anterior, utilizamos un mismo catch para atrapar excepciones de dos tipos diferentes. Usando este método se pueden atrapar 2, 3 o mas tipos de excepción, siempre separando cada tipo de excepción por un pipe. Hay una regla que se debe seguir en estos casos para que el compilador no se queje: Las excepciones que se quieren atrapar dentro de un mismo catch no deben estar relacionadas en el sentido de que una excepción no debe ser superclase de otra. Veamos un ejemplo, modifiquemos el código anterior para que se vea de la siguiente manera:
import java.util.Scanner;
public class Division {
public static void main(String[] args) {
try{
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
System.out.println("Resultado = " + (a/b));
}
catch(Exception | ArithmeticException e){ // Error!
System.out.println("Ocurrió un error. Bye!");
}
}
}
En el ejemplo anterior se modificó el catch para que atrape excepciones de tipo Exception y ArithmeticException, esto provocará un error en tiempo de compilación. La razón de esto es que en el árbol de jerarquía de las excepciones en Java, Exception es padre de ArithmeticException (de hecho es padre de todas las excepciones en Java), por lo cual el compilador ve que esto no tiene sentido porque si ocurre una excepción de tipo ArithmeticException, también seria una excepción de tipo Exception debido a la herencia entre ellas. El compilador al darse cuenta de esto simplemente no permitirá compilar el programa. Esto no ocurría en el ejemplo anterior a este en donde las excepciones que se atrapaban en el catch eran ArithmeticException y InputMismatchException, porque ninguna de esos dos tipos de excepción es padre uno del otro.
Ahora que ya hemos entendido que rayos que son los bloques try y catch, a llegado el momento de meternos con el bloque finally. A continuación se ilustra como se implementaría un bloque finally:
try{
...
}catch(SomeException e){
...
}finally{
// Hacer algo sin importar si hubo una excepción o no.
}
El bloque finally es un bloque opcional (de hecho catch también puede ser opcional, lo veremos mas adelante) que va después del try/catch y siempre se ejecuta sin importar si hubo una excepción o no. Debido a esta característica de finally, dicho bloque puede usarse para hacer cleanup de lo que sea que se este haciendo dentro de try/catch, por ejemplo, finally es un buen lugar para cerrar conexiones a bases de datos, cerrar archivos o liberar otro tipo de recursos. Imagínense que después de ejecutar cierto código de nuestra aplicación, sea necesario cerrar una conexión a base de datos. Sin finally tendríamos que poner el cerrado de esa conexión dentro de try y también dentro de catch, eso por si hay un error o no se pueda cerrar en ambos casos, aunque eso provoque que repliquemos el mismo código de cerrado en dos partes diferentes. Con finally podemos poner el cerrado de la conexión en tan solo un lugar sin importar si hay una excepción o no en nuestro código.
Hace un momento dije que lo que este dentro del bloque finally siempre se va a ejecutar sin importar si dentro del bloque try hubo un error o no, y esto se cumple incluso en casos bastante confusos en donde pareciera que no debería ser así. Veamos el siguiente código de ejemplo que recibe dos números y regresa un String con un mensaje indicando el resultado de la división entre ellos:
public static String divisionConMensaje(int a, int b){
try{
return "El resultado es " + (a/b);
}catch(ArithmeticException e){
System.out.println("Estoy dentro de catch");
return "Error en la división";
}
finally{
System.out.println("Estoy dentro de finally");
}
}
Si el parámetro b que se recibe en el método divisionConMensaje es un cero es obvio que provocara una excepción porque dividir entre cero causa un ArithmeticException, y por lo tanto entraría la ejecución del código dentro del bloque catch. Aquí el detalle es que dentro del catch hay un return antes del bloque finally. ¿Que va a pasar si llamamos ese método? ¿Acaso si se ejecutaría lo que esta en el bloque finally a pesar de que en el bloque catch estamos retornando ya un resultado para nuestro método? La respuesta es simple: Lo que esta dentro de finally si se va a ejectutar, no se dejen engañar por este tipo de preguntas, en especial si desean tomar alguna certificación de Java que ofrece Oracle, están plagadas de tricky questions como esta. Compliquemos un poco mas la situación, modifiquemos un poco ese método de la siguiente manera:
public static String divisionConMensaje(int a, int b){
try{
return "El resultado es " + (a/b);
}catch(ArithmeticException e){
System.out.println("Estoy dentro de catch");
return "Error en la división";
}
finally{
System.out.println("Estoy dentro de finally");
return "Mensaje desde el finally";
}
}
Aquí se pone la cosa algo confusa porque dentro del finally tenemos un return al igual que dentro de try y dentro de catch.
Nota: Es mala practica poner un return dentro de finally. Recuerda que finally es para hacer cleanup de tu código, osea para hacer cosas como cerrar conexiones a bases de datos, cerrar archivos, etc.
¿Entonces aquí que va a pasar? Ya sabemos que finally siempre se ejecuta pero, ¿Cual return va a ser el que se va a retornar en la ejecución del método? La respuesta es que este método retornará la cadena ” Mensaje desde el finally”. El return dentro de finally hace que se descarte el return del try (o del catch si es que hubo una excepción y el flujo del código entra a catch).
¿Bastante interesante no creen? Una vez que ya hemos comprendido lo que es y para sirve el trío de try/catch/finally, hablemos un poco de algo que quedo pendiente: ¿Cuando es obligatorio/opcional utilizar catch y finally junto con try? Anteriormente había comentado que finally es un bloque opcional, y de hecho catch también puede ser opcional. Aquí la regla que hay que recordar es la siguiente: un bloque try no puede estar solo, tiene que estar acompañado por al menos un bloque mas, el cual puede ser uno o mas bloques catch (como los montones de ejemplos que ya hemos visto), o puede ser uno o mas catchs con un finally (como el ejemplo anterior), ó finalmente puede estar acompañado por solamente un bloque finally (sin catchs), el cual se vería de la siguiente manera:
try{
// Código del catch
}finally{
// Código del finally
}
El código anterior es totalmente legal y compilará perfectamente, y por supuesto el bloque finally siempre se ejecutará sin importar si ocurrió una excepción en el try o no.
Antes de finalizar este post me gustaría comentar algo sobre el bloque finally. En todo este post he dicho que el bloque finally siempre se ejecuta sin importar nada. Bueno, esto no es del todo cierto. Se me ocurre una serie de escenarios en donde puede que finally nunca se ejecute y estos son:
- Terminar el programa con System.exit dentro de try o dentro de catch (siempre y cuando el flujo del código caiga en dicho catch) antes de que llegue a finally.
- Terminar el programa manualmente antes de que llegue a finally.
- Un OutOfMemoryError dentro del try o el catch.
- Quitar de la corriente eléctrica la computadora antes de que llegue a finally. 🙂
Espero que haya quedado más claro el manejo de excepciones en Java. Pronto estaré publicando la tercera parte de esta serie de posts sobre el manejo de excepciones en Java.
Saluditos!